%%%-------------------------------------------------------------------
%%% @author liuwentao
%%% @doc
%%%         处理服务器和web的交互
%%% @end
%%% Created : 21. 6月 2021 19:52
%%%-------------------------------------------------------------------
-module(web_conn).
-author("liuwentao").

-export([
    handle/1
    ,format_web_args/2
]
).

-include("common.hrl").
-define(web_key, "3vbz881dzoo21zb").
-define(max_len, 409600).


%%----------------------------------------------------
%% 对外接口
%%----------------------------------------------------
%% web通讯消息（PHP调用，取代peb）
%% web调用的参数格式是：[Platform, Ts, Ticket, ArgsFormat, M, F, A]
handle(Socket) ->
    case auth_ip(Socket) of
        true -> do_handle(Socket);
        false -> gen_tcp:close(Socket)
    end.
do_handle(Socket) ->
    case gen_tcp:recv(Socket, 2, 3000) of
        {error, _Err} ->
            ?ERR("web通讯失败:~w", [_Err]),
            gen_tcp:close(Socket);
        {ok, <<Len:16>>} when Len > ?max_len ->
            ?ERR("web通讯失败:数据过长[~w]", [Len]),
            gen_tcp:close(Socket);
        {ok, <<Len:16>>} ->
            ?DEBUG("web通讯成功:数据长度[~w]", [Len]),
            case gen_tcp:recv(Socket, Len, 8000) of
                {error, _Err} ->
                    ?ERR("web通讯失败:~w", [_Err]),
                    gen_tcp:close(Socket);
                {ok, Msg} ->
                    ?DEBUG("web发送过来的Msg:~ts", [Msg]),
                    %% decode出MFA，约定只有integer和bitstring两种类型的参数
                    %% PHP发送的格式应该是['module', 'function', [...]]
                    case catch do(Socket, Msg) of
                        ok -> ok;
                        _Err ->
                            ?ERR("web通讯:处理数据时发生异常Msg=~w Err:~w", [Msg, _Err])
                    end,
                    gen_tcp:close(Socket)
            end;
        {ok, _Data} ->
            ?ERR("web通讯:无效数据[~w]", [_Data]),
            gen_tcp:close(Socket)
    end.

do(Socket, Msg) ->
    Rtn = case json:decode(Msg) of
              [StrP, StrTs, StrTicket | Other] ->
                  P = StrP,
                  Ts = list_to_integer(util:to_list(StrTs)),
                  Ticket = StrTicket,
                  case auth_web_ticket(P, Ts, Ticket) of
                      false->
                          ?ERR("web通讯失败:ticket验证失败"),
                          {error, "ticket验证失败"};
                      true ->
                          {M1, F1, A1} = case Other of
                                             [AF, StrM, StrF, A] ->
                                                 M = util:to_atom(StrM),
                                                 F = util:to_atom(StrF),
                                                 NewA = format_web_args(AF, A),
                                                 {M, F, NewA};
                                             [_AF, StrM, StrF] ->
                                                 M = util:to_atom(StrM),
                                                 F = util:to_atom(StrF),
                                                 {M, F, []};
                                             _ ->
                                                 ?ERR("web通讯失败，MFA格式错误:~w", [Other]),
                                                 {undefined, undefined, []}
                                         end,
                          ?DEBUG("web===mod:~w fun=~w args=~w", [M1, F1, A1]),
                          put(sign, Ticket),
                          case auth_mfa(M1, F1) of
                              true -> erlang:apply(M1, F1, A1);
                              false -> {error, "非法的方法调用"}
                          end
                  end;
              _ ->
                  {error, "验证参数错误"}
          end,
    RtnData = format_web_result(Rtn),
    ?DEBUG("返回给web的原始结果:~w, 组装后的结果:~w", [Rtn, RtnData]),
    gen_tcp:send(Socket, RtnData),
    ok.

%%----------------------------------------------------
%% 私有函数
%%----------------------------------------------------
%% 格式化web调用erlang方法时的参数
%% AF :: [term()] 仅限于 i 和 b两种，可嵌套，跟peb一样用法，比如：[i, b, [{i, b, i}]]
format_web_args(AF, Args) ->
    {ok, NewAF} = util:string_to_term(AF),
    ?DEBUG("转换过的参数格式是：~w", [NewAF]),
    do_format_web_args(NewAF, Args).
do_format_web_args(AF, Args) when (not is_list(AF)) orelse (not is_list(Args)) ->
    ?ERR("AF 或 Args不是列表:~w, ~w", [AF, Args]),
    erlang:error(illegal_web_args);
do_format_web_args(AF, Args) when length(AF) =:= length(Args) ->
    do_v(AF, Args);
do_format_web_args(_AF, _Args) ->
    ?ERR("AF 和 Args长度不对应:~w, ~w", [_AF, _Args]),
    erlang:error(illegal_web_args).

do_v([I], Args) when is_list(Args) ->
    [do_v(I, A) || A <- Args];
do_v(F, Args) when is_list(F) ->
    L = lists:seq(1, length(F)),
    Args1 = case is_list(Args) of
                false -> util:to_list(Args);
                true -> Args
            end,
    [do_v(lists:nth(Idx, F), lists:nth(Idx, Args1)) || Idx <- L];
do_v(F, Args) when is_tuple(F) ->
    F1 = tuple_to_list(F),
    L = do_v(F1, Args),
    list_to_tuple(L);
do_v(F, Arg) ->
    do_do_v(F, Arg).
do_do_v(i, Arg) when is_number(Arg) -> Arg;
do_do_v(i, Arg) when is_list(Arg) -> list_to_integer(Arg);
do_do_v(b, Arg) when is_list(Arg) -> unicode:characters_to_binary(Arg);
do_do_v(b, Arg) when is_bitstring(Arg) -> unicode:characters_to_list(Arg);
do_do_v(b, Arg) -> util:term_to_bitstring(Arg).

%% 格式化返回给web的结果 -> json格式
format_web_result(ok) -> json:encode({struct, [{success, true}, {message, <<>>}]});
format_web_result(true) -> json:encode({struct, [{success, true}, {message, <<>>}]});
format_web_result({json, Json}) -> Json;
format_web_result({ok, Result}) -> json:encode({struct, [{success, true}, {message, Result}]});
format_web_result({true, Result}) -> json:encode({struct, [{success, true}, {message, Result}]});
format_web_result({error, Reason}) -> json:encode({struct, [{success, false}, {message, unicode:characters_to_binary(Reason)}]});
format_web_result({false, Reason}) -> json:encode({struct, [{success, false}, {message, unicode:characters_to_binary(Reason)}]});
format_web_result(Other) ->
    ?ERR("返回结果不规范，可能无法json encode成功:~w", [Other]),
    json:encode({struct, [{success, false}, {message, util:flist("接口返回格式不规范:~ts", [util:term_to_bitstring(Other)])}]}).

%% ---------------------------------------
%% 安全措施：
%% ---------------------------------------
%% web调用认证 -> true | false
auth_web_ticket(P, Ts, Ticket) ->
    ServerKey = ?web_key,
    E = date:unixtime() - Ts,
    T = util:md5(lists:concat([binary_to_list(P), Ts, ServerKey])),
    ?DEBUG("Key:~ts T:~ts Ticket:~ts", [ServerKey, T, Ticket]),
    E < 30 andalso Ticket =:= T.

%% 模块和方法白名单 -> true | false
auth_mfa(_, _) -> true.
%% auth_mfa(_, _) -> false.

%% TODO:检查IP -> true | false
%% 怎么维护这个后台IP白名单？
auth_ip(Socket) ->
    {ok, {_Ip, _Port}} = inet:peername(Socket),
    ?DEBUG("auth_ip:IP=~w, Port=~w", [_Ip, _Port]),
    true.
